package com.ztm.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.jwt.JWTUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ztm.shortlink.admin.common.context.BaseContext;
import com.ztm.shortlink.admin.common.convention.exception.ClientException;
import com.ztm.shortlink.admin.common.properties.JwtProperties;
import com.ztm.shortlink.admin.dao.entity.UserDO;
import com.ztm.shortlink.admin.dao.mapper.UserMapper;
import com.ztm.shortlink.admin.dto.req.UserGetCodeReqDTO;
import com.ztm.shortlink.admin.dto.req.UserLoginReqDTO;
import com.ztm.shortlink.admin.dto.req.UserRegisterReqDTO;
import com.ztm.shortlink.admin.dto.req.UserUpdateReqDTO;
import com.ztm.shortlink.admin.dto.resp.UserLoginRespDTO;
import com.ztm.shortlink.admin.dto.resp.UserRespDTO;
import com.ztm.shortlink.admin.service.UserService;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.ztm.shortlink.admin.common.constant.RedisCacheConstant.*;
import static com.ztm.shortlink.admin.common.enums.UserErrorCodeEnum.USER_NAME_EXITS;
import static com.ztm.shortlink.admin.common.enums.UserErrorCodeEnum.USER_SAVE_ERROR;

/**
 * 用户接口实现层
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final JwtProperties jwtProperties;
    private final GroupServiceImpl groupService;



    /**
     * 判断用户名是否已存在
     * @param username
     * @return
     */
    @Override
    public Boolean hasUserName(String username) {
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }


    /**
     * 获取短信验证码
     * @param requestParam
     * @return
     */
    @Override
    public void sendCode(UserGetCodeReqDTO requestParam) {

        //生成验证码
        String code = RandomUtil.randomNumbers(6);

        //调用第三方服务发送验证码

        //保存验证码到redis，设置验证码的有效时间
        stringRedisTemplate.opsForValue().set(SMS_VERIFICATION_CODE_KEY + requestParam.getPhone(),code,
                5L,TimeUnit.MINUTES);
    }


    /**
     * 用户注册
     * @param requestParam
     */
    @Override
    public void register(UserRegisterReqDTO requestParam) {
        //用户名已存在
        if(hasUserName(requestParam.getUsername())){
            throw new ClientException(USER_NAME_EXITS);
        }

        //判断验证码是否正确
        String code = requestParam.getCode();
        if(code==null||
                !code.equals(stringRedisTemplate.opsForValue()
                        .get(SMS_VERIFICATION_CODE_KEY + requestParam.getPhone()))){
            throw new ClientException("验证码有误");
        }

        //分布式锁，同一时刻大量请求使用相同的用户名进行注册，只有一个请求可以获取到锁
        RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + requestParam.getUsername());
        try {
            if(lock.tryLock()){
                int inserted = baseMapper.insert(BeanUtil.toBean(requestParam, UserDO.class));

                if(inserted<1){
                    throw new ClientException(USER_SAVE_ERROR);
                }
                //用户名注册后加入到布隆过滤器
                userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());

                //用户注册后自动创建默认分组
                groupService.groupSave(requestParam.getUsername(),"默认分组");
            }else {
                //未获取到锁，返回异常
                throw new ClientException(USER_NAME_EXITS);
            }
        } finally {
            lock.unlock();
        }

    }

    /**
     * 用户登录
     * @param requestParam
     * @return
     */
    @Override
    public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        //根据用户名和密码查询数据库
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, requestParam.getUsername())
                .eq(UserDO::getPassword, requestParam.getPassword())
                .eq(UserDO::getDelFlag,0);
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        //没有查询到
        if(userDO==null){
            throw new ClientException("用户名不存在或者密码错误");
        }

        //缓存记录用户已经登录
        Boolean hasLogin = stringRedisTemplate.hasKey(USER_LOGGED_IN_KEY + requestParam.getUsername());
        if(hasLogin!=null&&hasLogin){
            throw new ClientException("用户已登录");
        }

        //颁发token
        Map<String,Object> map = new HashMap<>();
        map.put("username",requestParam.getUsername());
        map.put("expire_time",System.currentTimeMillis() + jwtProperties.getTtl());
        String token = JWTUtil.createToken(map, jwtProperties.getSecretKey().getBytes());

        //用户已登录缓存记录，有效时间和token有效时间一致
        stringRedisTemplate.opsForValue().set(USER_LOGGED_IN_KEY + requestParam.getUsername(),USER_LOGGED_IN_FLAG,
                jwtProperties.getTtl(), TimeUnit.MILLISECONDS);

        return new UserLoginRespDTO(requestParam.getUsername(),token);
    }

    /**
     * 根据用户名查询用户信息
     * @return
     */
    @Override
    public UserRespDTO getUserInfo() {
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, BaseContext.getCurrentUsername());
        UserDO userDO = baseMapper.selectOne(queryWrapper);
        UserRespDTO userRespDTO = new UserRespDTO();
        BeanUtils.copyProperties(userDO,userRespDTO);
        return userRespDTO;
    }

    /**
     * 用户修改信息
     * @param requestParam
     */
    @Override
    public void update(UserUpdateReqDTO requestParam) {
        if(!requestParam.getUsername().equals(BaseContext.getCurrentUsername())){
            throw new ClientException("用户名不可修改");
        }
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, requestParam.getUsername());
        baseMapper.update(BeanUtil.toBean(requestParam,UserDO.class),queryWrapper);

    }



    /**
     * 用户退出登录
     */
    @Override
    public void logout() {
        //删除用户已登录的缓存记录
        stringRedisTemplate.delete(USER_LOGGED_IN_KEY + BaseContext.getCurrentUsername());
    }


}
